package common

import java.nio.charset.Charset
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter

import akka.event.Logging
import akka.event.LoggingAdapter
import akka.http.scaladsl.model.HttpRequest
import akka.http.scaladsl.model.RemoteAddress
import akka.http.scaladsl.server.Directives.extractClientIP
import akka.http.scaladsl.server.Directives.logRequestResult
import akka.http.scaladsl.server.directives.LogEntry
import akka.http.scaladsl.server.directives.LoggingMagnet
import akka.http.scaladsl.server.Directive0
import akka.http.scaladsl.server.RouteResult
import helpers.Logging

trait AccessLogging extends Logging {

  protected val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z")

  protected def accessLogging(log: LoggingAdapter): Directive0 =
    extractClientIP.flatMap { ip ⇒
      logRequestResult(
        LoggingMagnet(_ ⇒ requestTimeThenLog(ip, log))
      )
    }

  protected def requestTimeThenLog(
      ip: RemoteAddress,
      log: LoggingAdapter
  ): HttpRequest ⇒ (Any ⇒ Unit) = {
    val requestTime = System.currentTimeMillis()
    buildLogString(log, requestTime, ip)
  }

  protected def buildLogString(
      loggingAdapter: LoggingAdapter,
      requestTimestamp: Long,
      ip: RemoteAddress,
      level: Logging.LogLevel = Logging.InfoLevel
  )(req: HttpRequest)(res: Any): Unit = {
    val elapsed = System.currentTimeMillis() - requestTimestamp
    val (status, contentLength) = res match {
      case RouteResult.Complete(resp) ⇒
        (resp.status.intValue, resp.entity.contentLengthOption.getOrElse(0))
      case RouteResult.Rejected(_) ⇒
        //Should't get here due to explicit handleRejections in tree
        //but need to handle anyway
        (0, 0)
    }

    val ipAddress = ip.toOption.map(_.getHostAddress()).getOrElse("0.0.0.0")
    val dateTime  = ZonedDateTime.now().format(formatter)
    val path      = req.uri.path
    val params    = req.uri.queryString(Charset.defaultCharset()).map(q ⇒ s"?$q").getOrElse("")
    val proto     = req.protocol.value

    val logLine = s"""$ipAddress - [$dateTime] "${req.method
      .name()} $path$params $proto" $status $contentLength $elapsed"""
    LogEntry(logLine, level).logTo(loggingAdapter)
  }
}
